using System;
using System.ServiceModel.Channels;
using System.ServiceModel.Security;
using System.Xml;

namespace WcfExtensions.ServiceModel.Channels
{
    public sealed class CompressionBindingElement : BindingElement
    {
        public CompressionBindingElement()
        {
            Level = CompressionLevel.Normal;
            Algorithm = CompressionAlgorithm.GZip;
        }

        public CompressionBindingElement(CompressionAlgorithm algorithm, CompressionLevel level)
        {
            Algorithm = algorithm;
            Level = level;
        }

        private CompressionBindingElement(CompressionBindingElement elementToBeCloned)
            : base(elementToBeCloned)
        {
            Level = elementToBeCloned.Level;
            Algorithm = elementToBeCloned.Algorithm;
        }

        public CompressionLevel Level { get; set; }

        public CompressionAlgorithm Algorithm { get; set; }

        public override bool CanBuildChannelFactory<TChannelShape>(BindingContext context)
        {
            return context.CanBuildInnerChannelFactory<TChannelShape>();
        }

        public override bool CanBuildChannelListener<TChannelShape>(BindingContext context)
        {
            return context.CanBuildInnerChannelListener<TChannelShape>();
        }

        public override IChannelFactory<TChannelShape> BuildChannelFactory<TChannelShape>(BindingContext context)
        {
            if (!CanBuildChannelFactory<TChannelShape>(context))
            {
                throw new InvalidOperationException("Unsupported channel type");
            }
            if (context == null)
                throw new ArgumentNullException("context");

            var factory = new CompressionChannelFactory<TChannelShape>(this, context);
            return factory;
        }

        public override IChannelListener<TChannelShape> BuildChannelListener<TChannelShape>(BindingContext context)
        {
            if (!CanBuildChannelListener<TChannelShape>(context))
            {
                throw new InvalidOperationException("Unsupported channel type");
            }

            if (context == null)
                throw new ArgumentNullException("context");

            var listener = new CompressionChannelListener<TChannelShape>(this, context);
            return listener;
        }

        private static ChannelProtectionRequirements GetProtectionRequirements()
        {
            var requirements = new ChannelProtectionRequirements();
            var name =
                new XmlQualifiedName(WSCompressionStrings.Compression,
                                     WSCompressionStrings.NamespaceUri);

            var parts = new MessagePartSpecification(new [] {name});
            parts.MakeReadOnly();
            requirements.IncomingSignatureParts.AddParts(parts);
            requirements.OutgoingSignatureParts.AddParts(parts);
            return requirements;
        }

        public override BindingElement Clone()
        {
            return new CompressionBindingElement(this);
        }

        public override T GetProperty<T>(BindingContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");

            if (typeof (T) == typeof (ChannelProtectionRequirements))
            {
                ChannelProtectionRequirements protectionRequirements = GetProtectionRequirements();
                protectionRequirements.Add(context.GetInnerProperty<ChannelProtectionRequirements>() ?? new ChannelProtectionRequirements());
                return protectionRequirements as T;
            }

            return context.GetInnerProperty<T>();
        }
    }
}